<?php
/**
 * Copyright 2012  SURFfoundation
 * 
 * This file is part of ESCAPE.
 * 
 * ESCAPE is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * ESCAPE is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with ESCAPE.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * For more information:
 * http://escapesurf.wordpress.com/
 * http://www.surffoundation.nl/
 * 
 * Contact: d.vierkant@utwente.nl
 * 
 * @license http://www.gnu.org/licenses/gpl.html GNU GPLv3
 */
?>
<?php
/**
 * ORE aggregation
 * 
 * Represents an OAI ORE aggregation.
 */
class escape_repository_OreAggregation extends escape_repository_OreObject
{
	public static $predicate_owner = 'http://purl.utwente.nl/ns/escape-system.owl#owner';
	public static $predicate_editor = 'http://purl.utwente.nl/ns/escape-system.owl#editor';
	
	/**
	 * Constructs an ORE Aggregation object.
	 * @param escape_repository_Repository $repository Link to the repository object
	 * @param string $uri The URI for the data object
	 * @param simplerdf_RdfResource $rdfResource Prefetched RDF data for object creation
	 * @param boolean $noIndex If true, do not use the index to fetch the object data
	 * @param boolean $allowDeleted If true, also return deleted objects
	 * @return escape_repository_OreAggregation The ORE Aggregation object
	 */
	public function __construct(&$repository, $uri, $rdfResource = null, $noIndex = false, $allowDeleted = false)
	{
		parent::__construct($repository, $uri, $rdfResource, $noIndex, $allowDeleted);
		
		// perform a type check
		if(!in_array('info:fedora/' . $this->repository->config['system_object_namespace'] . ':cmodel.ore.Aggregation', $this->fedoraModels))
		{
			throw new escape_repository_exception_TypeMismatch("The specified resource is not a ORE Aggregation");
		}
	}

	/**
	 * Fetch the URI of the REM describing this aggregation.
	 * @return string URI of the REM describing this aggregation
	 */
	public function getRemExternalUri()
	{
		return $this->repository->config['server_url'] . '/rem/' . $this->getId();
	}
	
	/**
	 * Get the current aggregation. 
	 * @return escape_repository_OreAggregation Aggregation
	 */
	public function &getAggregation()
	{
		return $this;
	}
	
	/**
	 * Deletes this aggregation and all it's aggregated resources.
	 * @see escape/repository/escape_repository_DataObject::delete()
	 */
	public function delete()
	{
		// try to delete all aggregated resources before deleting this aggregation
		$aggregatedResources = $this->getAggregatedResources();
		foreach($aggregatedResources as $aggregatedResource)
		{
			try
			{
				$aggregatedResource->delete();
			}
			catch(Exception $ex)
			{
				// ignore
			}
		}
		
		// delete the aggregation
		parent::delete();
	}
	
	/**
	 * Fetches the resources aggregated by this aggregation.
	 * @param array $rdfTypes (optional) An array of RDF type URIs, if provided only resources which match one of these types will be returned 
	 * @return array The resources aggregated by this aggregation
	 */
	public function getAggregatedResources($rdfTypes = null)
	{
		$aggregatedResources = array();
		
		$aggregationUri = $this->getUri();
		
		$sparqlQueryTypeLimit = "";
		if($rdfTypes !== null && count($rdfTypes) > 0)
		{
			$rdfTypePatterns = array();
			foreach($rdfTypes as $rdfType)
			{
				$rdfTypePatterns[] = '{?s <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <' . $rdfType . '> .}';
			}
			$sparqlQueryTypeLimit = implode(' UNION ', $rdfTypePatterns);
		}
			
		// select all RDF triples belonging to the aggregated resources
		// double check if all resources are of the type "cmodel.ore.AggregatedResource"
		$sparqlQuery =
"construct { ?s ?p ?o }
where
{
?s ?p ?o .
?s <info:fedora/fedora-system:def/model#hasModel> <info:fedora/" . $this->repository->config['system_object_namespace'] . ":cmodel.ore.AggregatedResource> .
<" . $aggregationUri . "> <http://www.openarchives.org/ore/terms/aggregates> ?s .
" . $sparqlQueryTypeLimit . "
}";
		
		$rawAggregatedResourcesRdfDocument = $this->tripleStore->sparqlConstruct($sparqlQuery);
		$rawAggregatedResourceRdfResources = $rawAggregatedResourcesRdfDocument->getResources();
		foreach($rawAggregatedResourceRdfResources as $rawAggregatedResourceRdfResource)
		{
			$aggregatedResourceUri = $rawAggregatedResourceRdfResource->getSubject();
			$aggregatedResources[] = new escape_repository_OreAggregatedResource($this->repository, $aggregatedResourceUri, $rawAggregatedResourceRdfResource);
		}
			
		return $aggregatedResources;
	}
	
	/**
	 * Fetch an aggregated resource by it's external URI.
	 * @param string $externalUri External URI
	 * @return escape_repository_OreAggregatedResource Aggregated resource or null if no object was found
	 */
	public function getAggregatedResourceByExternalUri($externalUri)
	{
		$aggregatedResource = null;
		
		$aggregationUri = $this->getUri();
		
		// select all RDF triples belonging to the aggregated resources
		// double check if all resources are of the type "cmodel.ore.AggregatedResource"
		$sparqlQuery =
"construct { ?s ?p ?o }
where
{
?s ?p ?o .
?s <info:fedora/fedora-system:def/model#hasModel> <info:fedora/" . $this->repository->config['system_object_namespace'] . ":cmodel.ore.AggregatedResource> .
?s <http://purl.utwente.nl/ns/escape-system.owl#externalUri> <" . $this->tripleStore->sparqlEscapeUri($externalUri) . "> .
<" . $this->tripleStore->sparqlEscapeUri($aggregationUri) . "> <http://www.openarchives.org/ore/terms/aggregates> ?s .
}";
		
		$rawAggregatedResourcesRdfDocument = $this->tripleStore->sparqlConstruct($sparqlQuery);
		if(count($rawAggregatedResourcesRdfDocument->getResourceCount()) === 1)
		{
			$rawAggregatedResourceRdfResource = $rawAggregatedResourcesRdfDocument->getResource(0);
			
			$aggregatedResourceUri = $rawAggregatedResourceRdfResource->getSubject();
			$aggregatedResource = new escape_repository_OreAggregatedResource($this->repository, $aggregatedResourceUri, $rawAggregatedResourceRdfResource);
		}
			
		return $aggregatedResource;
	}
	
	/**
	 * Checks if the aggregation aggregates a resource with the specified URI.
	 * @param string $uri URI
	 * @return bool True if the aggregation aggregates a resource with the specified URI
	 */
	public function hasAggregatedResource($uri)
	{
		return $this->properties->hasStatement('http://www.openarchives.org/ore/terms/aggregates', $uri);
	}
	
	/**
	 * Creates a new aggregated resource and adds it to the aggregation.
	 * @param string $aggregatedResourceUri The URI of the resource to create, a URI will be generated automatically if none is specified
	 * @return escape_repository_OreAggregatedResource The new aggregated resource
	 */
	public function createAggregatedResource($aggregatedResourceUri = null)
	{
		// generate a new PID if no PID was specified
		if($aggregatedResourceUri === null)
		{
			$aggregatedResourceUri = $this->fedora->convertPidToUri($this->fedora->getNextPid(1, $this->repository->config['data_object_namespace']));
		}
		$aggregatedResourceId = $this->repository->convertObjectUriToDataObjectId($aggregatedResourceUri);
		$aggregatedResourcePid = $this->fedora->convertUriToPid($aggregatedResourceUri);
		
		// add the aggregated resource -> aggregation relation as a isPartOf relation to RELS-EXT
		$relsExtRdfResource = simplerdf_RdfResource::createRdfResource('#');
		$relsExtRdfResource->addStatement('info:fedora/fedora-system:def/relations-external#isPartOf', $this->getUri());
		
		// add the externalUri and resourceUri
		$semanticsRdfResource = simplerdf_RdfResource::createRdfResource('#');
		$semanticsRdfResource->addStatement('http://purl.utwente.nl/ns/escape-system.owl#externalUri', $this->repository->config['server_url'] . '/resource/' . $aggregatedResourceId);
		$semanticsRdfResource->addStatement('http://purl.utwente.nl/ns/escape-system.owl#resourceUri', $this->repository->config['server_url'] . '/resource/' . $aggregatedResourceId);
		
		// create an aggregated resource
		$aggregatedResourcePid = $this->repository->_createDataObject("ORE Aggregated Resource", array('info:fedora/' . $this->repository->config['system_object_namespace'] . ':cmodel.ore.Object', 'info:fedora/' . $this->repository->config['system_object_namespace'] . ':cmodel.ore.AggregatedResource'), $aggregatedResourcePid, $semanticsRdfResource, $relsExtRdfResource);
		
		// add it to the aggregation
		$rdfResource = $this->getProperties();
		$rdfResource->addStatement("http://www.openarchives.org/ore/terms/aggregates", $aggregatedResourceUri);
		$this->setProperties($rdfResource);
		
		// fetch the object without using the index
		$aggregatedResource = new escape_repository_OreAggregatedResource($this->repository, $aggregatedResourceUri, null, true);
		
		// update the index
		$aggregatedResource->updateIndex();
		
		return $aggregatedResource;
	}

	/**
	 * Create a new Relation Annotation.
	 * @param string $subjectUri Subject URI
	 * @param string $predicateUri Predicate URI
	 * @param string $objectUri Object URI
	 * @param string $aggregatedResourceUri The URI of the resource to create, a URI will be generated automatically if none is specified
	 * @return escape_repository_OreAggregatedResource Relation Annotation (aggregated resource)
	 */
	public function createRelationAnnotation($subjectUri, $predicateUri, $objectUri, $aggregatedResourceUri = null)
	{
		// create an aggregated resource
		$relationAnnotation = $this->createAggregatedResource($aggregatedResourceUri);
		
		$rdfResource = $relationAnnotation->getProperties();
		$rdfResource->addStatement('http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'http://purl.utwente.nl/ns/escape-annotations.owl#RelationAnnotation');
		$rdfResource->addStatement('http://purl.utwente.nl/ns/escape-annotations.owl#subject', $subjectUri);
		$rdfResource->addStatement('http://purl.utwente.nl/ns/escape-annotations.owl#predicate', $predicateUri);
		$rdfResource->addStatement('http://purl.utwente.nl/ns/escape-annotations.owl#object', $objectUri);
		$relationAnnotation->setProperties($rdfResource);
		
		return $relationAnnotation;
	}
	
	/**
	 * Get URIs for aggregated resource having the predicate / object combination specified. 
	 * @param $predicate Predicate URI
	 * @param $object Object URI / value
	 * @return array List of aggregated resource URIs
	 */
	public function getAggregatedResourceUrisHavingPredicateAndObject($predicate, $object)
	{
		$aggregatedResourceUris = array();
		
		$objectIsValidUri = escape_isValidUri($object);
		
		$sparqlQuery  = "select ?subject\n";
		$sparqlQuery .= "where\n";
		$sparqlQuery .= "{\n";
		$sparqlQuery .= "	{\n";
		$sparqlQuery .= "		<" . $this->tripleStore->sparqlEscapeUri($this->getUri()) . "> <http://www.openarchives.org/ore/terms/aggregates> ?subject .\n";
		$sparqlQuery .= "		?subject <" . $this->tripleStore->sparqlEscapeUri($predicate) . "> '" . $this->tripleStore->sparqlEscapeLiteral($object) . "' .\n";
		$sparqlQuery .= "	}\n";
		if($objectIsValidUri)
		{
			$sparqlQuery .= "	union\n";
			$sparqlQuery .= "	{\n";
			$sparqlQuery .= "		<" . $this->tripleStore->sparqlEscapeUri($this->getUri()) . "> <http://www.openarchives.org/ore/terms/aggregates> ?subject .\n";
			$sparqlQuery .= "		?subject <" . $this->tripleStore->sparqlEscapeUri($predicate) . "> <" . $this->tripleStore->sparqlEscapeUri($object) . "> .\n";
			$sparqlQuery .= "	}\n";
		}
		$sparqlQuery .= "}";
		
		$sparqlResultSet = $this->tripleStore->sparqlSelect($sparqlQuery);
		$results = $sparqlResultSet->getResults();
		
		foreach($results as $result)
		{
			$aggregatedResourceUris[] = $result['subject']['value']; 
		}
		
		return $aggregatedResourceUris;
	}
	
	/**
	 * Get the list of all RDF types occuring in this aggregation.
	 * @return array List of all (distinct) RDF types occuring in this aggregation
	 */
	public function getAggregatedResourceRdfTypes()
	{
		$rdfTypes = array();
		
		$sparqlQuery  = "select distinct ?rdfType\n";
		$sparqlQuery .= "where\n";
		$sparqlQuery .= "{\n";
		$sparqlQuery .= "	<" . $this->getUri() . "> <http://www.openarchives.org/ore/terms/aggregates> ?aggregatedResourceUri .\n";
		$sparqlQuery .= "	?aggregatedResourceUri <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> ?rdfType .\n";
		$sparqlQuery .= "}";

		$sparqlResultSet = $this->tripleStore->sparqlSelect($sparqlQuery);
		$results = $sparqlResultSet->getResults();
		
		foreach($results as $result)
		{
			$rdfTypes[] = $result['rdfType']['value']; 
		}
		
		return $rdfTypes;
	}
	
	/**
	 * Get the list of root classes for all RDF types occuring in this aggregation.
	 * @return array List of root classes for all (distinct) RDF types occuring in this aggregation
	 */
	public function getAggregatedResourceEscapeRootClassUris()
	{
		$rootClassUris = array();
		
		$rdfTypes = $this->getAggregatedResourceRdfTypes();
		foreach($rdfTypes as $rdfType)
		{
			$rootClassUris[] = $this->repository->repositorySchema->getEscapeRootClassUri($rdfType);
		}
		$rootClassUris = array_unique($rootClassUris);
		
		return $rootClassUris;
	}
	
	/**
	 * Get the URIs of aggegation owners.
	 * @return array URIs of aggegation owners
	 */
	public function getOwners()
	{
		$rdfResource = $this->getProperties();
		
		return $rdfResource->getObjects(self::$predicate_owner);
	}
	
	/**
	 * Check if the user specified by an URI is an owner of this aggregation.
	 * @param string $owner User URI
	 * @return boolean True if the user specified by an URI is an owner of this aggregation
	 */
	public function hasOwner($owner)
	{
		$rdfResource = $this->getProperties();
		
		return $rdfResource->hasStatement(self::$predicate_owner, $owner);
	}

	/**
	 * Replaces the owners of this aggregation with the user URIs specified. 
	 * @param array $owners List of user URIs
	 */
	public function setOwners($owners)
	{
		$rdfResource = $this->getProperties();
		
		$rdfResource->removeStatement(self::$predicate_owner);
		$rdfResource->addStatement(self::$predicate_owner, $owners);
		
		$this->setProperties($rdfResource);
	}

	/**
	 * Add the user specified by a URI as an owner of this aggregation.
	 * @param $owner User URI
	 */
	public function addOwner($owner)
	{
		$rdfResource = $this->getProperties();
		
		$rdfResource->addStatement(self::$predicate_owner, $owner);
		
		$this->setProperties($rdfResource);
	}

	/**
	 * Remove the user specified by a URI as an owner of this aggregation
	 * @param string $owner User URI
	 */
	public function removeOwner($owner)
	{
		$rdfResource = $this->getProperties();
		
		$rdfResource->removeStatement(self::$predicate_owner, $owner);
		
		$this->setProperties($rdfResource);
	}
	
	/**
	 * Get the URIs of aggegation editors.
	 * @return array URIs of aggegation editors
	 */
	public function getEditors()
	{
		$rdfResource = $this->getProperties();
		
		return $rdfResource->getObjects(self::$predicate_editor);
	}
	
	/**
	 * Check if the user specified by an URI is an editor of this aggregation.
	 * @param string $editor User URI
	 * @return boolean True if the user specified by an URI is an editor of this aggregation
	 */
	public function hasEditor($editor)
	{
		$rdfResource = $this->getProperties();
		
		return $rdfResource->hasStatement(self::$predicate_editor, $editor);
	}
	
	/**
	 * Replaces the editors of this aggregation with the user URIs specified. 
	 * @param array $editors List of user URIs
	 */
	public function setEditors($editors)
	{
		$rdfResource = $this->getProperties();
		
		$rdfResource->removeStatement(self::$predicate_editor);
		$rdfResource->addStatement(self::$predicate_editor, $editors);
		
		$this->setProperties($rdfResource);
	}
	
	/**
	 * Add the user specified by a URI as an editor of this aggregation.
	 * @param string $editor User URI
	 */
	public function addEditor($editor)
	{
		$rdfResource = $this->getProperties();
		
		$rdfResource->addStatement(self::$predicate_editor, $editor);
		
		$this->setProperties($rdfResource);
	}
	
	/**
	 * Remove the user specified by a URI as an editor of this aggregation
	 * @param string $editor User URI
	 */
	public function removeEditor($editor)
	{
		$rdfResource = $this->getProperties();
		
		$rdfResource->removeStatement(self::$predicate_editor, $editor);
		
		$this->setProperties($rdfResource);
	}
	
	/**
	 * Clone an aggregation.
	 * @return escape_repository_OreAggregation Cloned aggregation
	 */
	public function cloneAggregation()
	{
		// fetch the old aggregated resources
		$oldAggregatedResources = $this->getAggregatedResources();

		$newAggregation = null;
		$uriMappingTable = array();
		$newAggregatedResourcesByUri = array();
		try
		{
			// create a new aggregation
			$newAggregation = $this->repository->createOreAggregation();
			
			// for each old aggregated resource, create a new aggregated resource and store the old and new uris in a mapping table
			foreach($oldAggregatedResources as $oldAggregatedResource)
			{
				/* @var $oldAggregatedResource escape_repository_OreAggregatedResource */
				$newAggregatedResource = $newAggregation->createAggregatedResource();
				$newAggregatedResourcesByUri[$newAggregatedResource->getUri()] = $newAggregatedResource;
				
				$uriMappingTable[$oldAggregatedResource->getUri()] = $newAggregatedResource->getUri();
			}
			
			// foreach old aggregated resource, copy the properties and attachment to the new aggregated resource 
			foreach($oldAggregatedResources as $oldAggregatedResource)
			{
				/* @var $oldAggregatedResource escape_repository_OreAggregatedResource */
				$oldProperties = $oldAggregatedResource->getProperties();
				
				// fetch the new aggregated resource
				$oldUri = $oldAggregatedResource->getUri();
				$newUri = $uriMappingTable[$oldUri];
				/* @var $newAggregatedResource escape_repository_OreAggregatedResource */
				$newAggregatedResource = $newAggregatedResourcesByUri[$newUri];

				// copy attachment
				$oldAttachmentFileName = $oldAggregatedResource->getAttachmentFileName();
				if($oldAttachmentFileName !== null)
				{
					$oldAttachmentMimeType = $oldAggregatedResource->getAttachmentMimeType();
					
					// save the old attachment to a temporary file
					$tempFileName = tempnam('', 'escape');
					$tempFile = fopen($tempFileName, 'w');
					$oldAggregatedResource->copyAttachment($tempFile);
					fclose($tempFile);
					
					// upload the attachment to the new aggregated resource
					$newAggregatedResource->saveAttachmentByReference($tempFileName, $oldAttachmentMimeType, $oldAttachmentFileName);
					
					// remove the temporary file
					unlink($tempFileName);
				}
				
				// copy all non-assignable properties (everything which you can edit in the editor) + creation / modification metadata + relation annotation properties
				$newProperties = $newAggregatedResource->getProperties();
				$predicates = $oldProperties->getPredicates();
				foreach($predicates as $predicate)
				{
					if(preg_match('~^http://purl.utwente.nl/ns/escape-system.owl#(createdBy|lastModifiedBy|creationDate|modificationDate|template)$~', $predicate) || preg_match('~^http://purl.utwente.nl/ns/escape-annotations.owl#~', $predicate) || !$this->repository->repositorySchema->isNonAssignableProperty($predicate))
					{
						$objects = $oldProperties->getObjects($predicate);
						foreach($objects as $object)
						{
							if(isset($uriMappingTable[$object]))
							{
								$newProperties->addStatement($predicate, $uriMappingTable[$object]);
							}
							else
							{
								$newProperties->addStatement($predicate, $object);
							}
						}
					}
				}
				
				// copy the external uri if it's not equal to the resource uri
				$oldExternalUri = $oldAggregatedResource->getExternalUri();
				$oldResourceUri = $oldAggregatedResource->getResourceUri();
				if($oldExternalUri !== $oldResourceUri)
				{
					$newProperties->removeStatement('http://purl.utwente.nl/ns/escape-system.owl#externalUri');
					$newProperties->addStatement('http://purl.utwente.nl/ns/escape-system.owl#externalUri', $oldExternalUri);
				}

				// copy the RDF types
				$newProperties->setRdfType($oldProperties->getRdfTypes());

				// save the new properties
				$newAggregatedResource->setProperties($newProperties);
			}
			
			// copy properties from the old aggregation to the new aggregation
			$oldProperties = $this->getProperties();
			$newProperties = $newAggregation->getProperties();
			$predicates = $oldProperties->getPredicates();
			foreach($predicates as $predicate)
			{
				// copy all non-assignable properties (everything which you can edit in the editor) + creation / modification metadata
				if(preg_match('~^http://purl.utwente.nl/ns/escape-system.owl#(createdBy|lastModifiedBy|creationDate|modificationDate|template)$~', $predicate) || !$this->repository->repositorySchema->isNonAssignableProperty($predicate))
				{
					$objects = $oldProperties->getObjects($predicate);
					foreach($objects as $object)
					{
						if(isset($uriMappingTable[$object]))
						{
							$newProperties->addStatement($predicate, $uriMappingTable[$object]);
						}
						else
						{
							$newProperties->addStatement($predicate, $object);
						}
					}
				}
			}
			
			// copy the RDF types
			$newProperties->setRdfType($oldProperties->getRdfTypes());
			
			// save the new properties
			$newAggregation->setProperties($newProperties);
		}
		catch(Exception $ex)
		{
			// if any error occurs, delete all resources which were created during the clone process
			
			foreach($newAggregatedResourcesByUri as $newAggregatedResources)
			{
				try
				{
					$newAggregatedResources->delete();
				}
				catch(Exception $ex)
				{
					// ignore
				}
			}
			if($newAggregation !== null)
			{
				try
				{
					$newAggregation->delete();
				}
				catch(Exception $ex)
				{
					// ignore
				}
			}
			
			// rethrow the exception
			throw $ex;
		}
		
		return $newAggregation;
	}
	
	/**
	 * Replaces all externalUris which are equal to the resourceUris with a blank node URI.
	 * @param simplerdf_RdfDocument $rdfDocument
	 */
	protected function convertResourceUrisToBlankNodes(&$rdfDocument)
	{
		//TODO: ESCAPE objects with attachments should not be converted to blank nodes!
		
		$bnodeIdx = 1;
		$rdfResources =& $rdfDocument->getResources();
		
		// replaces all externalUris which are equal to the resourceUris with a blank node URI
		foreach($rdfResources as $rdfResource)
		{
			/* @var $rdfResource simplerdf_RdfResource */
			if(!$rdfResource->hasRdfType('http://www.openarchives.org/ore/terms/Aggregation'))
			{
				$externalUri = $rdfResource->getFirstObject('http://purl.utwente.nl/ns/escape-system.owl#externalUri');
				$resourceUri = $rdfResource->getFirstObject('http://purl.utwente.nl/ns/escape-system.owl#resourceUri');
				
				if($externalUri === $resourceUri)
				{
					$rdfResource->replaceObject('http://purl.utwente.nl/ns/escape-system.owl#externalUri', $externalUri, '_:b' . $bnodeIdx);
					$bnodeIdx++;
				}
			}
		}
		
		// remove blank nodes from the aggregation ore:aggregates statements
		foreach($rdfResources as $rdfResource)
		{
			/* @var $rdfResource simplerdf_RdfResource */
			if($rdfResource->hasRdfType('http://www.openarchives.org/ore/terms/Aggregation'))
			{
				$aggregatedResourceUris = $rdfResource->getObjects('http://www.openarchives.org/ore/terms/aggregates');
				foreach($aggregatedResourceUris as $aggregatedResourceUri)
				{
					$aggregatedResource = $rdfDocument->getResourceBySubject($aggregatedResourceUri);
					$externalUri = $aggregatedResource->getFirstObject('http://purl.utwente.nl/ns/escape-system.owl#externalUri');
					
					if(preg_match('/^_:/', $externalUri))
					{
						$rdfResource->removeStatement('http://www.openarchives.org/ore/terms/aggregates', $aggregatedResourceUri);
					}
				}
				
				break;
			}
		}
	}
	
	/**
	 * Generates an ORE Resource Map document in RDF/XML format.
	 * @return simplerdf_RdfDocument ORE Resource Map document
	 */
	public function generateResourceMapDocument($replacementTitlePredicate = null)
	{
		$aggregationUri = $this->getUri();
		
		// select all RDF triples belonging to this aggregation and the resources it aggregates
		// double check if all resources are of the type "cmodel.ore.Object"
		$sparqlQuery =
"construct
{
<" . $aggregationUri . "> ?aP ?aO .
?arS ?arP ?arO .
}
where
{
{
<" . $aggregationUri . "> ?aP ?aO .
<" . $aggregationUri . "> <info:fedora/fedora-system:def/model#hasModel> <info:fedora/" . $this->repository->config['system_object_namespace'] . ":cmodel.ore.Object> .
}
union
{
?arS ?arP ?arO .
?arS <info:fedora/fedora-system:def/model#hasModel> <info:fedora/" . $this->repository->config['system_object_namespace'] . ":cmodel.ore.Object> .
<" . $aggregationUri . "> <http://www.openarchives.org/ore/terms/aggregates> ?arS.
}
}";
		
		$rawRemRdfDocument = $this->tripleStore->sparqlConstruct($sparqlQuery);

//TODO: is this desirable behaviour?
//		// convert resources without an external URI to blank nodes
//		$this->convertResourceUrisToBlankNodes($rawRemRdfDocument);
		
		// fetch the RDF statements for this resource map
		$aggregationRdfResource = $rawRemRdfDocument->getResourceBySubject($aggregationUri);
		
		// create an empty target RDF document
		$rdfDocument = simplerdf_RdfDocument::createRdfDocument();

		// create a REM object 
		$remUri = $this->getRemExternalUri();
		$remRdfResource = $rdfDocument->createResource($remUri);
		$remRdfResource->addStatement('http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'http://www.openarchives.org/ore/terms/ResourceMap');
		$remRdfResource->addStatement('http://purl.org/dc/terms/created', $this->createdDate);
		$remRdfResource->addStatement('http://purl.org/dc/terms/modified', $this->lastModifiedDate);
		$remRdfResource->addStatement('http://www.openarchives.org/ore/terms/describes', $this->getExternalUri());

		if(isset($this->repository->config['rem_creator_name']) || isset($this->repository->config['rem_creator_page']))
		{
			// create a REM creator agent object
			$remCreatorRdfResource = $rdfDocument->createResource();
			if(isset($this->repository->config['rem_creator_name']))
			{
				$remCreatorRdfResource->addStatement('http://xmlns.com/foaf/0.1/name', $this->repository->config['rem_creator_name']);
			}
			if(isset($this->repository->config['rem_creator_page']))
			{
				$remCreatorRdfResource->addStatement('http://xmlns.com/foaf/0.1/page', $this->repository->config['rem_creator_page']);
			}
			
			// link the REM creator agent object to the REM object
			$remRdfResource->addStatement('http://purl.org/dc/terms/creator', $remCreatorRdfResource->getSubject());
		}
		
		// create an empty uri mapping table
		$internalExternalUriMappingTable = array();
		
		// start to recursively build the resource map
		$this->generateResourceMapDocumentRecursive($rdfDocument, $aggregationRdfResource, $internalExternalUriMappingTable, $rawRemRdfDocument, $replacementTitlePredicate);
		
		// return the resource map in RDF XML
		return $rdfDocument;
	}
	
	/**
	 * Recursively build a resource map, following links to internal IDs
	 * @param simplerdf_RdfDocument $outputDocument Target RDF document
	 * @param simplerdf_RdfResource $currentResource Current RDF resource
	 * @param array $internalExternalUriMappingTable URI mapping table
	 * @param simplerdf_RdfDocument $rawRemRdfDocument Prefetched RDF document containing all resources needed for this resource map 
	 */
	private function generateResourceMapDocumentRecursive(&$outputDocument, &$currentResource, &$internalExternalUriMappingTable, &$rawRemRdfDocument, $replacementTitlePredicate = null)
	{
		// fetch the external URI for this object
		$externalUri = $currentResource->getFirstObject('http://purl.utwente.nl/ns/escape-system.owl#externalUri');
		// continue processing only if the current object has an external URI
		if($externalUri !== null)
		{
			// store the internal - external URI mapping
			$internalExternalUriMappingTable[$currentResource->getSubject()] = $externalUri;
			
			// create a new resource in the output document
			$rdfResource = $outputDocument->createResource($externalUri);
			
			// fetch the ID of the current resource
			$currentResourceId = preg_replace('/^[^:]+:/', '', $this->fedora->convertUriToPid($currentResource->getSubject()));
			
			// add a statement for the ESCAPE ID
			$rdfResource->addStatement('http://purl.utwente.nl/ns/escape-system.owl#id', $currentResourceId);

			// add a statement for the ESCAPE resource URI
			$rdfResource->addStatement('http://purl.utwente.nl/ns/escape-system.owl#resourceUri', $currentResource->getObjects('http://purl.utwente.nl/ns/escape-system.owl#resourceUri'));

			// add creation and modification dates to aggregation and resource map objects
			if($currentResource->hasRdfType('http://www.openarchives.org/ore/terms/Aggregation') || $currentResource->hasRdfType('http://www.openarchives.org/ore/terms/ResourceMap'))
			{
				$createdDate = $currentResource->getFirstObject('info:fedora/fedora-system:def/model#createdDate');
				if($createdDate !== null)
				{
					$rdfResource->addStatement('http://purl.org/dc/terms/created', $createdDate);
				}
				$lastModifiedDate = $currentResource->getFirstObject('info:fedora/fedora-system:def/view#lastModifiedDate');
				if($lastModifiedDate !== null)
				{
					$rdfResource->addStatement('http://purl.org/dc/terms/modified', $lastModifiedDate);
				}
			}
			
			// add titles to relation annotations
			if($currentResource->hasRdfType('http://purl.utwente.nl/ns/escape-annotations.owl#RelationAnnotation'))
			{
				try
				{
					$relationAnnotation = $this->repository->getOreAggregatedResourceByUri($currentResource->getSubject());
					$relationAnnotationTitleTriple = $relationAnnotation->getTitle(true);
					$currentResource->addStatement($relationAnnotationTitleTriple['P'], $relationAnnotationTitleTriple['o']);
				}
				catch(Exception $ex)
				{
					// ignore
				}
			}
			
//			// add RDF type http://purl.org/info:eu-repo/semantics/EnhancedPublication for aggregations
//			if($rdfType === 'http://www.openarchives.org/ore/terms/Aggregation')
//			{
//				$rdfResource->addStatement('http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'http://purl.org/info:eu-repo/semantics/EnhancedPublication');
//			}
			
			// if we need to replace title properties, fetch the list of title properties for this object
			$titlePropertyUris = null;
			if($replacementTitlePredicate !== null)
			{
				$titlePropertyUris = $this->repository->repositorySchema->getClassTitlePropertyUriList($currentResource->getRdfTypes());
			}
			
			// fetch the predicates for this resource
			$predicates = $currentResource->getPredicates();
			
			// loop over all predicates
			foreach($predicates as $predicate)
			{
				// process the predicate only if it is NOT a Fedora statement and not a Hidden, System or Non Assignable property unless it's a ORE, RDF or Relation Annotation statement
				if(
					!preg_match('~^info:fedora/~', $predicate)
					&&
					(
						preg_match('~^(http://www.openarchives.org/ore/terms/|http://www.w3.org/1999/02/22-rdf-syntax-ns#|http://purl.utwente.nl/ns/escape-annotations.owl#)~', $predicate)
						||
						!$this->repository->repositorySchema->isNonAssignableProperty($predicate)
					)
				)
				{
					// fetch all objects for this predicate
					$objects = $currentResource->getObjects($predicate);

					// if we need to replace title properties, check if this predicate is in the list of title properties for this object
					if($titlePropertyUris !== null && in_array($predicate, $titlePropertyUris))
					{
						// replace the predicate
						$predicate = $replacementTitlePredicate;
					}
					
					// loop over all objects
					foreach($objects as $object)
					{
						// is it an internal reference?
						if(preg_match('~^info:fedora/~', $object))
						{
							// check if we've already processed the referenced resource before
							if(isset($internalExternalUriMappingTable[$object]))
							{
								// we've processed the referenced resource before, just add a reference to it's external URI
								$rdfResource->addStatement($predicate, $internalExternalUriMappingTable[$object]);
							}
							else
							{
								// we didn't process the referenced resource before
								try
								{
									$relatedRdfResource = $rawRemRdfDocument->getResourceBySubject($object);
									
									if($relatedRdfResource)
									{
										// check if the related resource has an external URI
										$relatedExternalUri = $relatedRdfResource->getFirstObject('http://purl.utwente.nl/ns/escape-system.owl#externalUri');
										if($relatedExternalUri !== null)
										{
											// add a reference to the related object
											$rdfResource->addStatement($predicate, $relatedExternalUri);
	
											// recurse to process the related object
											$this->generateResourceMapDocumentRecursive($outputDocument, $relatedRdfResource, $internalExternalUriMappingTable, $rawRemRdfDocument, $replacementTitlePredicate);
										}
									}
									else
									{
										// referenced object not found, probably a broken reference
									}
								}
								catch(Exception $exception)
								{
									// ignore errors, probably a broken reference
								}
							}
						}
						else
						{
							// not an internal reference, simply copy the statement

//TODO: the visualizer doesn't accept this format
//							// wrap dcterms:creator values which are not an internal reference into a blank node
//							if($predicate === 'http://purl.org/dc/terms/creator')
//							{
//								// create a blank node
//								$creatorRdfResource = $outputDocument->createResource();
//								$creatorRdfResource->addStatement('http://xmlns.com/foaf/0.1/name', $object);
//								//$creatorRdfResource->setRdfType('http://xmlns.com/foaf/0.1/Person');
//								
//								// link the blank node
//								$rdfResource->addStatement('http://purl.org/dc/terms/creator', $creatorRdfResource->getSubject());
//							}
//							else
//							{
								$rdfResource->addStatement($predicate, $object);
//							}
						}
					}
				}
			}
		}
	}
}
